package org.nicta.learn.factorization.OptimalGreedyResidualFitting;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;

import org.nicta.datasetLoader.MovieLens.MovieLensDataLoader;
import org.nicta.datasetLoader.MovieLens.MovieLensHelper;
import org.nicta.datasetLoader.MovieLens.MovieLensInfo;

import cern.colt.matrix.DoubleFactory1D;
import cern.colt.matrix.DoubleFactory2D;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.linalg.Algebra;
import cern.jet.math.Functions;
import de.bwaldvogel.liblinear.Feature;
import de.bwaldvogel.liblinear.FeatureNode;
import de.bwaldvogel.liblinear.Linear;
import de.bwaldvogel.liblinear.Model;
import de.bwaldvogel.liblinear.Parameter;
import de.bwaldvogel.liblinear.Problem;
import de.bwaldvogel.liblinear.SolverType;

public class OGRFFactorization {
	
	private int dimension;
	private String ratingsFileLocation;
	private MovieLensDataLoader mlLoader;

	DoubleMatrix2D U;
	DoubleMatrix2D V;
	Algebra algebra;
	
	public HashMap<Integer[], Double> ratings;
	public HashMap<Integer[], Double> testRatings;
	private HashMap<Integer, HashSet<Integer>> userRatedItems;
	private HashMap<Integer, HashSet<Integer>> itemsUsersMap;
	private HashMap<Integer, HashMap<Integer, Double>> itemUserRatings;
	
	public OGRFFactorization(int dimension, String location) throws Exception{
		this.dimension = dimension;
		this.ratingsFileLocation = location;
		movielensLoader("100K");
		testRatings = createTestSet(ratings,0.1);
	}
	
	public void movielensLoader(String datasetType) throws Exception{
		mlLoader = new MovieLensDataLoader(datasetType, ratingsFileLocation,
				null, null);
		mlLoader.loadData();
		ratings = mlLoader.getRatings();
		userRatedItems = mlLoader.getUserRatedMoviesMap();
		itemsUsersMap = mlLoader.getMoviesUsersMap();
		itemUserRatings = mlLoader.getmovieUserRatings();
		algebra = new Algebra();
		//U = DoubleFactory2D.dense.make(1, mlLoader.getUsersCount());
		//V = DoubleFactory2D.dense.make(1, mlLoader.getMoviesCount());
	
	}

	private HashMap<Integer[], Double> createTestSet(
			HashMap<Integer[], Double> ratings, double fraction ){
		HashMap<Integer[], Double> testRatings;
		testRatings = new HashMap<Integer[], Double>();
		Object[] ratingsKey  = ratings.keySet().toArray();
		double testSetSize = fraction * ratings.keySet().size();
		Random rand = new Random();
		int key;
		while(testRatings.size() < testSetSize){
			key = rand.nextInt(ratingsKey.length);
			Object userItemPair = ratingsKey[key];
			//TODO:needs optimization by the use of HashSet
			if(!ratings.containsKey(userItemPair)) continue;
			Integer userId = ((Integer[]) userItemPair)[0];
			Integer movieId = ((Integer[]) userItemPair)[1];
			if(itemsUsersMap.get(movieId).size() > 1 && 
					userRatedItems.get(userId).size() > 1){

				Double rating =  ratings.get(userItemPair);
				ratings.remove(userItemPair);
				userRatedItems.get(userId).remove(movieId);
				itemsUsersMap.get(movieId).remove(userId);
				testRatings.put(new Integer[]{userId, movieId}, rating);
			
			}
		}
		return testRatings;
	}
	
	public HashMap<Integer[], Double> getResidue(){
		double rating;
		double predicted;
		if(U == null || V == null){
			return ratings;
		}
		HashMap<Integer[], Double> residue = new HashMap<Integer[], Double>();
        Object[] ratingsKey  = ratings.keySet().toArray();
		for(Object userItemPair: ratingsKey){
			Integer userId = ((Integer[]) userItemPair)[0];
			Integer itemId = ((Integer[]) userItemPair)[1];
			predicted = algebra.mult(U.viewColumn(userId-1),
					V.viewColumn(itemId-1));
			rating = itemUserRatings.get(itemId).get(userId); 
			residue.put(new Integer[]{userId,itemId}, rating - predicted);
			System.out.println(rating - predicted);
		}
		
		return residue;
	}
	
	public void ogrf() throws Exception{
		HashMap<Integer[], Double> residue;
		DoubleMatrix2D[] UV;
		for(int i=0; i<dimension;i++){
			residue = getResidue();
			UV = linearRegression(residue);
			UV[0].assign(Functions.exp);
			UV[1].assign(Functions.exp);
			if(U == null || V == null){
				U = UV[0];
				V = UV[1];
			}
			else{
				U = DoubleFactory2D.dense.appendRows(U, UV[0]);
				V = DoubleFactory2D.dense.appendRows(V, UV[1]);
			}

		}
		System.out.println(U.size());
		System.out.println(V.size());

	}
	public double calculateTestError(){
		double error = 0;
		Object[] ratingsKey  = testRatings.keySet().toArray();
		for(Object userItemPair: ratingsKey){
			Integer userId = ((Integer[]) userItemPair)[0];
			Integer movieId = ((Integer[]) userItemPair)[1];
			Double rating =  testRatings.get(userItemPair);
		}
		return error;
	}
	
	public DoubleMatrix2D[] linearRegression(
			HashMap<Integer[], Double>  residue) throws Exception
	{
			DoubleMatrix2D[] result = new DoubleMatrix2D[2];
			Problem problem = new Problem();
			problem.n = mlLoader.getMoviesCount() + mlLoader.getUsersCount();
			problem.l = ratings.size();
			problem.x = new FeatureNode[problem.l][];
	        problem.y = new double[problem.l];
	        int count = 0;
	        Object[] ratingsKey  = residue.keySet().toArray();
			for(Object userItemPair: ratingsKey){
				Integer userId = ((Integer[]) userItemPair)[0];
				Integer movieId = ((Integer[]) userItemPair)[1];
				Double rating =  residue.get(userItemPair);
        		problem.x[count] = new FeatureNode[2];
        		Integer uid = userId + mlLoader.getMoviesCount();
        		problem.x[count][0] = new FeatureNode(movieId, 1);
        		problem.x[count][1] = new FeatureNode(uid, 1);
        		problem.y[count] = Math.log(residue.get(userItemPair));
        		count++;
	        }

	        SolverType solver = SolverType.L2R_L2LOSS_SVR_DUAL;
	        double C = 1.0;
	        double eps = 0.01;
	        Parameter parameter = new Parameter(solver, C, eps);
	        Model model = Linear.train(problem, parameter);
	        double[] weights = model.getFeatureWeights();
	        double[] v = Arrays.copyOfRange(weights, 0, mlLoader.getMoviesCount());
	        double[] u = Arrays.copyOfRange(weights, mlLoader.getMoviesCount(), weights.length);
	        result[0] = DoubleFactory2D.dense.make(u,1);
	        result[1] = DoubleFactory2D.dense.make(v,1);
	        return result;
	       
	}
	
}
